package com.gz.droolsrule.service;

import com.baomidou.mybatisplus.toolkit.CollectionUtils;
import com.gz.droolsrule.exception.DroolsExceptionEnum;
import com.gz.droolsrule.pojo.*;
import com.gz.droolsrule.pojo.fact.RuleExecutionObject;
import com.gz.droolsrule.util.DroolsUtil;
import com.gz.droolsrule.util.RuleUtils;
import com.gz.droolsrule.util.spring.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.drools.core.base.RuleNameStartsWithAgendaFilter;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;

/**
 * ClassName: DroolsRuleEngineService <br/>
 * Description: 规则引擎主要实现Service<br/>
 * date: 2019/5/31 17:01<br/>
 *
 * @author gz<br                               />
 * @since JDK 1.8
 */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class DroolsRuleEngineService {
    // 提取常量
    private static final String IMPORT = "import";
    //换行符
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
    //特殊处理的规则属性(字符串)
    private static final String[] arr = new String[]{"date-effective", "date-expires", "dialect", "activation-group", "agenda-group", "ruleflow-group"};
    @Autowired
    private RuleActionInfoService ruleActionInfoService;
    @Autowired
    private RuleActionParamInfoService actionParamInfoService;
    @Autowired
    private RuleConditionService ruleConditionService;
    @Autowired
    private RuleEntityService ruleEntityService;
    @Autowired
    private RuleInfoService ruleInfoService;
    @Autowired
    private RuleEntityItemService ruleEntityItemService;
    @Autowired
    private RuleActionParamValueService ruleActionParamValueService;
    @Autowired
    private RuleSceneEntityRelService ruleSceneEntityRelService;


    /**
     * @return
     * @Author gz
     * @Description 规则引擎执行方法
     * @Date 2019/5/31 18:25
     * @Param RuleExecutionObject fact对象信息
     * @Param scene 场景
     */
    public RuleExecutionObject excute(RuleExecutionObject object, String scene) throws Exception {
        try {
            KieSession kieSession = DroolsUtil.getInstance().getDrlSessionInCache(scene);
            if (kieSession != null) {
                // 直接执行
                return executeRuleEngine(kieSession, object, scene);
            } else {
                // 重新编译规则执行
                return compileRule(object, scene);
            }
        } catch (Exception e) {
            log.error("规则引擎执行出错：", e);
            throw new Exception(DroolsExceptionEnum.DROOLS_EXECUTE_FAIL_ERROR.getMessage());
        }
    }

    /**
     * @return
     * @Author gz
     * @Description 规则执行方法
     * @Date 2019/5/31 18:28
     * @Param KieSession 会话
     * @Param RuleExecutionObject fact对象
     * @Param scene 场景
     */
    public RuleExecutionObject executeRuleEngine(KieSession kieSession, RuleExecutionObject object, String scene) throws Exception {
        try {
            // 1.插入全局对象
            Map<String, Object> globalMap = object.getGlobalMap();
            for (Map.Entry<String, Object> entry : globalMap.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                log.info("插入Global对象：{}<==>{}", key, value.getClass().getName());
                kieSession.setGlobal(key, value);
            }
            // 2.插入fact对象
            // 2.1插入业务fact对象
            List<Object> objectList = object.getFactObjectList();
            for (Object fact : objectList) {
                log.info("插入fact对象：{}", fact.getClass().getName());
                kieSession.insert(fact);
            }
            // 2.2将 RuleExecutionObject 也插入到规则中，回调使用
            kieSession.insert(object);
            // 3.将规则涉及的所有动作实现类插入到规则中(如果没有，则不处理)
            BaseRuleSceneInfo sceneInfo = new BaseRuleSceneInfo();
            sceneInfo.setSceneIdentify(scene);
            // 根据场景获取所有的动作信息
            List<BaseRuleActionInfo> ruleActionList = this.ruleActionInfoService.findRuleActionListByScene(sceneInfo);
            for (BaseRuleActionInfo actionInfo : ruleActionList) {
                // 构建动作
                builderDroolAction(kieSession, actionInfo);
            }
            // 执行规则
            // 是否全部执行
            if (object.isExecuteAll()) {
                kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter(object.getRuleName()));
            }
            return object;
        } catch (Exception e) {
            log.error("规则引擎执行出错：", e);
            throw new Exception(DroolsExceptionEnum.DROOLS_EXECUTE_FAIL_ERROR.getMessage());
        } finally {
            // 释放资源
            kieSession.dispose();
        }
    }

    /**
     * @return
     * @Author gz
     * @Description 构建动作相关信息
     * @Date 2019/6/1 10:03
     * @Param session
     * @Param actionInfo 动作
     */
    private void builderDroolAction(KieSession session, BaseRuleActionInfo actionInfo) throws Exception {
        try {
            if (actionInfo.getActionType() == 1) {
                DroolsActionService actionService = SpringContextUtils.getBeanByName(actionInfo.getActionClazzIdentify());
                // DroolsActionService actionService = new TestActionImpl();
                session.insert(actionService);
            }
        } catch (BeansException e) {
            log.error("解析规则动作对象时出错，请查看{}", actionInfo);
            throw new Exception(DroolsExceptionEnum.DROOLS_BUILDER_ACTION_FAIL_ERROR.getMessage());
        }
    }

    /**
     * @return RuleExecutionObject
     * @Author gz
     * @Description 编译规则脚本，并执行规则
     * @Date 2019/6/1 10:04
     * @Param StringBuilder
     * @Param RuleExecutionObject fact对象
     * @Param scene 场景
     */
    private RuleExecutionObject compileRuleAndexEcuteRuleEngine(StringBuilder droolRuleStr
            , RuleExecutionObject ruleExecutionObject, final String scene) throws Exception {
        KieSession session;
        try {
            session = DroolsUtil.getInstance().getDrlSession(droolRuleStr.toString(), scene);
        } catch (Exception e) {
            throw new Exception(DroolsExceptionEnum.DROOLS_INIT_RULE_FAIL_ERROR.getMessage());
        }
        return this.executeRuleEngine(session, ruleExecutionObject, scene);

    }

    /**
     * @return
     * @Author gz
     * @Description 拼接drools语句
     * @Date 2019/6/1 10:22
     * @Param ruleExecutionObject
     * @Param scene 场景
     */
    private RuleExecutionObject compileRule(RuleExecutionObject ruleExecutionObject, final String scene) throws Exception {
        // 拼接规则脚本
        StringBuilder droolStr = new StringBuilder();
        log.info(LINE_SEPARATOR, "============重新拼接规则串============");
        // 1.引入包路径
        droolStr.append("package.com.drools.rules").append(";").append(LINE_SEPARATOR);
        // 2.引入Global全局对象
        droolStr.append("import com.gz.droolsrule.pojo.fact.RuleExecutionResult").append(";").append(LINE_SEPARATOR);
        droolStr.append("global java.util.Map _result").append(";").append(LINE_SEPARATOR);
        // 将RuleExecutionObject 引入 封装规则全局传参对象
        droolStr.append("import com.gz.droolsrule.pojo.fact.RuleExecutionObject").append(";").append(LINE_SEPARATOR);
        // 3.引入实体信息（根据场景获取相关的实体信息）
        // 传参
        BaseRuleSceneInfo sceneInfo = new BaseRuleSceneInfo(); // 规则引擎场景实体
        sceneInfo.setSceneIdentify(scene); // 设置场景标识
        // 根据场景获取相关的实体信息(表关系 场景<* ---- *>实体)
        List<BaseRuleEntityInfo> entityListByScene = this.ruleSceneEntityRelService.findBaseRuleEntityListByScene(sceneInfo); // 根据场景信息获取相关的实体信息
        log.info("场景对应的实体个数为：{}", entityListByScene.size());
        // 4.根据场景加载可用的规则信息(表关系 场景<1 ---- *>规则)
        List<BaseRuleInfo> ruleListByScene = this.ruleInfoService.findBaseRuleListByScene(sceneInfo); // 根据场景获取对应的规则信息
        log.info("场景可用规则个数为：{}", ruleListByScene.size());
        // 5.根据实体信息先组合drools的import语句
        this.insertImportInfo(droolStr, entityListByScene, sceneInfo);
        // 6.遍历并拼出每个规则的执行drools串
        for (BaseRuleInfo baseRuleInfo : ruleListByScene) {
            StringBuffer ruleTemp;
            ruleTemp = this.getDroolsInfoByRule(baseRuleInfo);
            droolStr.append(ruleTemp);
        }
        log.info(LINE_SEPARATOR, "===========================规则串================================", LINE_SEPARATOR);
        log.info(LINE_SEPARATOR, droolStr, LINE_SEPARATOR);
        System.out.println("===========================规则串================================");
        System.out.println(LINE_SEPARATOR + droolStr + LINE_SEPARATOR);
        System.out.println("===========================规则串================================");
        log.info(LINE_SEPARATOR, "===========================规则串================================", LINE_SEPARATOR);
        // 7.初始化drools，将实体对象扔进引擎
        return this.compileRuleAndexEcuteRuleEngine(droolStr, ruleExecutionObject, scene);
    }

    /**
     * @return
     * @Author gz
     * @Description 插入规则表达式的import部分
     * @Date 2019/6/1 11:02
     * @Param droolStr 规则串
     * @Param entityInfos 实体信息
     * @Param sceneInfo 场景信息
     */
    private void insertImportInfo(StringBuilder droolStr, List<BaseRuleEntityInfo> entityInfos, BaseRuleSceneInfo sceneInfo) {
        // 1.导入场景对应的实体类
        for (BaseRuleEntityInfo entityInfo : entityInfos) {
            droolStr.append(IMPORT).append(" ").append(entityInfo.getPkgName()).append(";").append(LINE_SEPARATOR);
        }
        // 2.导入基本类
        droolStr.append(IMPORT).append(" ").append("java.lang.String").append(";").append(LINE_SEPARATOR);
        droolStr.append(IMPORT).append(" ").append("java.util.Map").append(";").append(LINE_SEPARATOR);
        droolStr.append(IMPORT).append(" ").append("java.util.List").append(";").append(LINE_SEPARATOR);
        // 3.导入动作类
        // 3.1 根据场景获取所有的动作信息
        List<BaseRuleActionInfo> listByScene = this.ruleActionInfoService.findRuleActionListByScene(sceneInfo);
        if (CollectionUtils.isNotEmpty(listByScene)) {
            Boolean implFlag = false;
            // 循环处理
            for (BaseRuleActionInfo baseRuleActionInfo : listByScene) {
                // 如果是实现动作类，则先打标记
                if (!implFlag && baseRuleActionInfo.getActionType() == 1) {
                    implFlag = true;
                }
                // 拼接动作类
                droolStr.append(IMPORT).append(" ").append(baseRuleActionInfo.getActionClass()).append(";").append(LINE_SEPARATOR);
            }
            // 如果有实现类，则把实现类接口拼装进去
            if (implFlag) {
                droolStr.append(IMPORT).append(" ").append("com.gz.droolsrule.service.DroolsActionService").append(";").append(LINE_SEPARATOR);
            }
        }
    }

    /**
     * @return
     * @Author gz
     * @Description 组装规则信息
     * @Date 2019/6/1 11:04
     * @Param
     */
    private StringBuffer getDroolsInfoByRule(BaseRuleInfo ruleInfo) {
        // 拼接规则字符串
        StringBuffer buffer = new StringBuffer();
        // 1.拼接规则自身属性信息
        this.insertRuleInfo(buffer, ruleInfo);
        // 2.拼接条件
        this.insertRuleCondition(buffer, ruleInfo);
        // 3.拼接动作
        this.insertRuleActionInfo(buffer, ruleInfo);
        return buffer;
    }

    /**
     * @return
     * @Author gz
     * @Description 根据规则拼接规则自身相关的属性信息
     * @Date 2019/6/1 11:18
     * @Param ruleStr 规则字符串
     * @Param ruleInfo 规则
     */
    private void insertRuleInfo(StringBuffer ruleStr, BaseRuleInfo ruleInfo) {
        // 1.拼接规则名称（默认带双引号）
        ruleStr.append(LINE_SEPARATOR).append("rule").append(" ").append("\"").append(ruleInfo.getRuleName()).append("\"").append(LINE_SEPARATOR);
        // 拼接自身属性
        // 根据规则获取已经配置的属性信息()
        List<BaseRulePropertyRelInfo> propertyListByRuleId = this.ruleInfoService.findRulePropertyListByRuleId(ruleInfo.getRuleId());
        if (CollectionUtils.isNotEmpty(propertyListByRuleId)) {
            for (BaseRulePropertyRelInfo info : propertyListByRuleId) {
                // 如果配置的属性参数是字符串，单独处理
                if (ArrayUtils.contains(arr, info.getRulePropertyIdentify())) {
                    ruleStr.append("  ").append(info.getRulePropertyIdentify()).append(" ").append("\"").append(info.getRulePropertyValue()).append("\"").append(LINE_SEPARATOR);
                } else {
                    ruleStr.append("  ").append(info.getRulePropertyIdentify()).append(" ").append(info.getRulePropertyValue()).append(LINE_SEPARATOR);
                }
            }
        }
    }

    /**
     * @return
     * @Author gz
     * @Description 拼接规则条件信息
     * @Date 2019/6/1 11:20
     * @Param ruleStr 规则字符串
     * @Param baseRuleInfo 规则
     */
    private void insertRuleCondition(StringBuffer ruleStr, BaseRuleInfo baseRuleInfo) {
        // 1.拼接when条件
        ruleStr.append(LINE_SEPARATOR).append("when").append(LINE_SEPARATOR);
        // 参数
        ruleStr.append(LINE_SEPARATOR).append("$fact:RuleExecutionObject()").append(LINE_SEPARATOR);
        // 2.拼接实现类的动作条件（如果有实现类的动作，此处拼接动作接口）
        Integer count = this.ruleActionInfoService.findRuleActionCountByRuleIdAndActionType(baseRuleInfo.getRuleId());
        if (count > 0) {
            ruleStr.append("$action").append(":").append("DroolsActionService()").append(LINE_SEPARATOR);
        }
        // 3.根据规则id获取条件信息
        List<BaseRuleConditionInfo> ruleConditionInfoList = this.ruleConditionService.findRuleConditionInfoByRuleId(baseRuleInfo.getRuleId());
        // 如果没有找到条件信息，则默认永远满足
        if (CollectionUtils.isNotEmpty(ruleConditionInfoList)) {
            this.insertRuleConditionFromList(ruleStr, ruleConditionInfoList);
        } else {
            ruleStr.append("eval(true)").append(LINE_SEPARATOR);
        }
    }

    /**
     * @Author gz
     * @Description 处理条件部分内容
     * @Date 2019/6/4 17:37
     * @Param ruleStr 规则串
     * @Param conList 条件集合
     * @return
     */
    private void insertRuleConditionFromList(StringBuffer ruleStr, List<BaseRuleConditionInfo> conList) {
        // 只保存条件内容
        StringBuilder sb = new StringBuilder();
        // 实体id
        Long entityId = null;
        // 默认或的关系
        String relation = "&&";
        // 暂时先按照多个条件处理（目前只实现&&关系条件）
        for (int i = 0; i < conList.size(); i++) {
            BaseRuleConditionInfo conditionInfo = conList.get(i);
            // 表达式
            String expression = conditionInfo.getConditionExpression();
            // 先处理==、>=、<=、>、<、！=后面的变量
            String ofVariable = RuleUtils.getConditionOfVariable(expression);
            if (!RuleUtils.checkStyleOfString(ofVariable)) {
                expression = expression.replace(ofVariable, "'" + ofVariable + "'");
            }
            // 1.获取条件参数（比如：$21$ ，21 代表实体属性表id）
            List<String> list = RuleUtils.getConditionParamBetweenChar(conditionInfo.getConditionExpression());
            for (String itemId : list) {
                // 2.根据itemId获取实体的属性信息
                BaseRuleEntityItemInfo itemInfoById = this.ruleEntityItemService.findBaseRuleEntityItemInfoById(Long.valueOf(itemId));
                if (entityId == null || !entityId.equals(itemInfoById.getEntityId())) {
                    entityId = itemInfoById.getEntityId();
                }
                // 3.拼接属性字段（例如：$21$ > 20 替换成 age > 20）
                expression = expression.replace("$" + itemId + "$", itemInfoById.getItemIdentify());
            }
            // 如果是最后一个，则不拼接条件之间的关系
            if (i == conList.size() - 1) {
                relation = "";
            }
            // 4.拼接条件样式(比如 ： age > 20 && )
            sb.append(expression).append(" ").append(relation).append(" ");
        }
        // 获取实体
        BaseRuleEntityInfo info = this.ruleEntityService.findBaseRuleEntityInfoById(entityId);
        // 5.拼接实体类,完成条件拼接（例如：$User( age > 20 && sex==1) ）
        // 注意: 日期格式需要单独处理
        ruleStr.append("$").append(info.getEntityIdentify()).append(":").append(info.getEntityClazz()).append("(").append(sb).append(")").append(LINE_SEPARATOR);

    }

    /**
     * @return
     * @Author gz
     * @Description 拼接规则动作部分
     * @Date 2019/6/1 11:20
     * @Param ruleStr 规则字符串
     * @Param baseRuleInfo 规则
     */
    private void insertRuleActionInfo(StringBuffer ruleStr, BaseRuleInfo baseRuleInfo) {
        // 1.拼接then
        ruleStr.append(LINE_SEPARATOR).append("then").append(LINE_SEPARATOR);
        // 2.根据规则获取动作信息
        List<BaseRuleActionInfo> actionInfoList = this.ruleActionInfoService.findRuleActionListByRule(baseRuleInfo.getRuleId());
        // 如果没有获取到动作信息，则默认动作部分为空
        if (CollectionUtils.isEmpty(actionInfoList)) {
            ruleStr.append(LINE_SEPARATOR).append("end").append(LINE_SEPARATOR);
        } else {
            // 是否有实现类动作
            Boolean implFlag = false;
            // 临时动作对象
            BaseRuleActionInfo actionInfo;
            // 3.循环处理每个动作
            for (BaseRuleActionInfo info : actionInfoList) {
                actionInfo = info;
                // 4.获取动作参数信息
                implFlag = getRuleActionParam(ruleStr, implFlag, actionInfo);
            }
            // 7.拼接实现类接口
            if (implFlag) {
                ruleStr.append("$action").append(".").append("execute").append("($fact,_result)").append(";").append(LINE_SEPARATOR);
            }
            // 8.拼装尾部标识
            ruleStr.append("end").append(LINE_SEPARATOR).append(LINE_SEPARATOR).append(LINE_SEPARATOR);
        }
    }

    /**
     * @return
     * @Author gz
     * @Description 获取动作参数信息
     * @Date 2019/6/1 11:58
     * @Param implFlag 是否有实现类动作
     * @Param actionInfo 动作
     */
    private Boolean getRuleActionParam(StringBuffer ruleStr, Boolean implFlag, BaseRuleActionInfo actionInfo) {
        // 4.获取动作参数
        BaseRuleActionParamInfo paramInfo; // 动作参数对象
        BaseRuleActionParamValueInfo paramValue; // 动作参数值
        List<BaseRuleActionParamInfo> paramList = this.actionParamInfoService.findRuleActionParamByActionId(actionInfo.getActionId());
        for (BaseRuleActionParamInfo actionParamInfo : paramList) {
            paramInfo = actionParamInfo;
            // 5.获取动作参数的值信息
            paramValue = this.ruleActionParamValueService.findRuleParamValueByActionParamId(actionParamInfo.getActionParamId());
            /*
              6.1 动作实现类；
              如果value值包含##（例如：#3# * 5），那么就认为是： 获取item属性等于3 的实体属性，然后 乘以 5, 然后放进全局map里
              如果value值只是普通变量（例如：100、"asd"），那么就认为是；直接当作value放进全局map里
              6.2 自身动作类
              如果value值包含##（例如：#3# * 5），那么就认为是： 获取item属性等于3 的实体属性，然后 乘以 5, 然后set到自身属性上（例如：order.setMoney(order.getMoney() * 5)）
              如果value值只是普通变量（例如：100、"asd"），那么就认为是；直接当作value set到自身属性里 （例如：mes.setMessage("asd")）
             */

            String realValue = null;
            // 判断value值包含 ## （列如：#3# * 5），如果包含，首先取出item属性
            if (RuleUtils.checkContainOfOperator(paramValue.getParamValue(), "#")) {
                String tempValue = paramValue.getParamValue();
                // #3#之间的值
                List<String> strList = RuleUtils.getActionParamBetweenChar(tempValue);
                // 定义StringBuilder
                StringBuilder sb;
                for (String itemId : strList) {
                    sb = new StringBuilder();
                    BaseRuleEntityItemInfo itemInfo = this.ruleEntityItemService.findBaseRuleEntityItemInfoById(Long.valueOf(itemId));
                    // 获取实体
                    BaseRuleEntityInfo entityInfo = this.ruleEntityService.findBaseRuleEntityInfoById(itemInfo.getEntityId());
                    // 获取真实value表达式
                    sb.append("$").append(entityInfo.getEntityIdentify()).append(".")
                            .append(RuleUtils.getMethodByProperty(itemInfo.getItemIdentify()));
                    // 将表达式 #3# * 5 替换成  order.setMoney(order.getMoney() * 5)
                    realValue = tempValue.replace("#" + itemId + "#", sb.toString());
                }
            } else {
                realValue = paramValue.getParamValue();
                // 如果是字符串，则添加双引号
                if (!RuleUtils.checkStyleOfString(realValue)) {
                    realValue = "\"" + realValue + "\"";
                }

            }
            // 如果是实现类动作，则把参数放到全局变量 _result map中
            if (actionInfo.getActionType() == 1) {
                implFlag = true;
                ruleStr.append("_result.getMap().put(\"").append(paramInfo.getParamIdentify())
                        .append("\",").append(realValue)
                        .append(");")
                        .append(LINE_SEPARATOR);
            } else {
                ruleStr.append("$").append(actionInfo.getActionClazzIdentify()).append(".")
                        .append(RuleUtils.setMethodByProperty(paramInfo.getParamIdentify()))
                        .append("(").append(realValue).append(");")
                        .append(LINE_SEPARATOR);
            }
        }
        return implFlag;
    }
}
